%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Functional Reactive Programming in Oz

% This version implements FRP with a global synchronization
% to avoid glitches

% Author: Peter Van Roy
% Date: May 30, 2006

% FRP is a generalization of functional programming where an
% argument value can be replaced by a more recent value (this
% is called an "event") and any changes have to be propagated
% to other functions. If a new input value arrives and the
% output does not change then no new output value is generated.
% This is a kind of dataflow programming.  It can handle
% nondeterminism, so it is strictly more expressive than
% declarative concurrency.  It can be expressed with two new
% concepts: (1) nondeterministic choice and (2) synchronizing
% on a set of threads all suspending at once.  The first is
% implemented here using WaitTwo and the second is implemented
% here using computation spaces with the askVerbose operation.

% FRP is similar in expressiveness to concurrent logic
% programming.  The main difference is that FRP adds scheduling
% constraints to avoid temporarily violating invariants.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% First part: Local FRP operation

% Function with two input streams and one output stream
% If a new event arrives on the input, a new event is generated
% on the output, but only if the output changes

% Waits until at least one argument is bound
% Can return 1 if X is bound and 2 if Y is bound
declare
fun {WaitTwo X Y}
   {Record.waitOr X#Y}
end

% When a stream terminates with nil, it means there are no more changes
% Remembers last output and generates new output only if it is different
declare
fun {TwoArgOp F}
   fun {$ As Bs}
     thread
        fun {OpLoop Aval Bval Oval As Bs}
          case {WaitTwo As Bs}
          of 1 then
             case As of A|Ar then Nval={F A Bval} in
                if Oval==Nval then
                   {OpLoop A Bval Oval Ar Bs}
                else
                   Nval|{OpLoop A Bval Nval Ar Bs}
                end
             [] nil then
                % Slightly nonoptimal: May give repeats if one stream is done
                % (Fixing this is an exercise for the reader!)
                {Map Bs fun {$ B} {F Aval B} end}
             end
          [] 2 then
             case Bs of B|Br then Nval={F Aval B} in
                if Oval==Nval then
                   {OpLoop Aval B Oval As Br}
                else
                   Nval|{OpLoop Aval B Nval As Br}
                end
             [] nil then
                % Slightly nonoptimal: May give repeats if one stream is done
                {Map As fun {$ A} {F A Bval} end}
             end
          end
        end
        fun {FirstValue As}
           case As of A|Ar then A#Ar else if {IsInt As} then As#nil end end
        end
        % Wait until each stream has its first value
        Aval#Anext={FirstValue As}
        Bval#Bnext={FirstValue Bs}
        Oval={F Aval Bval}
     in
        % Execution loop
        Oval|{OpLoop Aval Bval Oval Anext Bnext}
     end
   end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Second part: Global FRP synchronization

% Avoid glitches by using computation spaces to synchronize
% on suspension of all threads.  The output is read only when
% all threads are suspended.

% Return current last element of a stream
declare
fun {End S}
   case S of X|S2 then
      if {IsDet S2} then {End S2} else X end
   end
end

% Return unbound tail of a stream
declare
fun {Tail S}
   if {IsDet S} then {Tail S.2} else S end
end

% Example calculation inside of a computation space
declare P1 As P2 Bs Sp in
{NewPort As P1} % Input event stream
{NewPort Bs P2} % Output event stream
Sp={Space.new proc {$ _} Add Div Rs in
                 % Define calculation
                 % Example: r=(a div 2)+(a div 3)
                 Add={TwoArgOp fun {$ A B} A+B end}
                 Div={TwoArgOp fun {$ A B} A div B end}
                 Rs={Add {Div As 2} {Div As 3}}
                 thread for X in Rs do {Port.send P2 X} end end
              end}

% One step of a calculation
% Inject a new input event; then calculate internally and
% possibly generate a new output event.  Avoid glitches
% by using askVerbose to wait until all threads inside the
% computation space are simultaneously suspended.  Then
% detect whether a new output event was generated or not.
declare
proc {Step X}
   Z={Tail Bs} % Get unbound tail of output stream
in
   {Port.send P1 X} % Inject an input event
   {Wait {Space.askVerbose Sp}} % Wait for fixpoint
   % Detect if an output event was generated:
   if {IsDet Z} then {Browse outevent({End Bs})}
   else {Browse nooutevent} end
end

% Example: Insert some input events
{Step 10}
{Step 11}
{Step 12}
{Step 13}
{Step 14}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
